package net.aspw.client.features.module.impl.exploit

import net.aspw.client.event.EventTarget
import net.aspw.client.event.PacketEvent
import net.aspw.client.event.UpdateEvent
import net.aspw.client.event.WorldEvent
import net.aspw.client.features.module.Module
import net.aspw.client.features.module.ModuleCategory
import net.aspw.client.features.module.ModuleInfo
import net.aspw.client.utils.MovementUtils
import net.aspw.client.utils.PacketUtils
import net.aspw.client.utils.misc.RandomUtils
import net.aspw.client.utils.timer.MSTimer
import net.aspw.client.value.BoolValue
import net.aspw.client.value.IntegerValue
import net.aspw.client.value.ListValue
import net.minecraft.network.INetHandler
import net.minecraft.network.Packet
import net.minecraft.network.play.INetHandlerPlayClient
import net.minecraft.network.play.INetHandlerPlayServer
import java.util.*

@ModuleInfo(name = "FakeLag", spacedName = "Fake Lag", category = ModuleCategory.EXPLOIT)
class FakeLag : Module() {

    private val fakeLagMode = ListValue("Mode", arrayOf("All", "InBound", "OutBound"), "All")
    private val fakeLagMoveOnly = BoolValue("MoveOnly", false)

    private val minRand: IntegerValue = object : IntegerValue("MinDelay", 170, 0, 20000, "ms") {
        override fun onChanged(oldValue: Int, newValue: Int) {
            val v = maxRand.get()
            if (v < newValue) set(v)
        }
    }
    private val maxRand: IntegerValue = object : IntegerValue("MaxDelay", 500, 0, 20000, "ms") {
        override fun onChanged(oldValue: Int, newValue: Int) {
            val v = minRand.get()
            if (v > newValue) set(v)
        }
    }

    private val fakeLagInclude = BoolValue("Include", true)
    private val fakeLagExclude = BoolValue("Exclude", false)

    // variables
    private val outBus = LinkedList<Packet<INetHandlerPlayServer>>()
    private val inBus = LinkedList<Packet<INetHandlerPlayClient>>()

    private val ignoreBus = LinkedList<Packet<out INetHandler>>()

    private val inTimer = MSTimer()
    private val outTimer = MSTimer()

    private var inDelay = 0
    private var outDelay = 0

    override fun onEnable() {
        inBus.clear()
        outBus.clear()
        ignoreBus.clear()

        inTimer.reset()
        outTimer.reset()
    }

    override fun onDisable() {
        while (inBus.size > 0)
            inBus.poll()?.processPacket(mc.netHandler)

        while (outBus.size > 0) {
            val upPacket = outBus.poll() ?: continue
            PacketUtils.sendPacketNoEvent(upPacket)
        }

        inBus.clear()
        outBus.clear()
        ignoreBus.clear()
    }

    @EventTarget(priority = -100)
    fun onPacket(event: PacketEvent) {
        mc.thePlayer ?: return
        mc.theWorld ?: return
        val packet = event.packet
        if (ignoreBus.remove(packet)) return

        if ((fakeLagMode.get().equals("outbound", true) || fakeLagMode.get().equals("all", true))
            && packet::class.java.simpleName.startsWith("C", true)
            && (!fakeLagInclude.get() || "c0f,confirmtransaction,packetplayer,c17".split(",")
                .find { packet::class.java.simpleName.contains(it, true) } != null)
            && (!fakeLagExclude.get() || "c0f,confirmtransaction,packetplayer,c17".split(",")
                .find { packet::class.java.simpleName.contains(it, true) } == null)
        ) {
            outBus.add(packet as Packet<INetHandlerPlayServer>)
            ignoreBus.add(packet)
            event.cancelEvent()
        }

        if ((fakeLagMode.get().equals("inbound", true) || fakeLagMode.get().equals("all", true))
            && packet::class.java.simpleName.startsWith("S", true)
            && (!fakeLagInclude.get() || "c0f,confirmtransaction,packetplayer,c17".split(",")
                .find { packet::class.java.simpleName.contains(it, true) } != null)
            && (!fakeLagExclude.get() || "c0f,confirmtransaction,packetplayer,c17".split(",")
                .find { packet::class.java.simpleName.contains(it, true) } == null)
        ) {
            inBus.add(packet as Packet<INetHandlerPlayClient>)
            ignoreBus.add(packet)
            event.cancelEvent()
        }
    }

    @EventTarget
    fun onWorld(event: WorldEvent) {
        inBus.clear()
        outBus.clear()
        ignoreBus.clear()

        inTimer.reset()
        outTimer.reset()
    }

    @EventTarget(priority = -5)
    fun onUpdate(event: UpdateEvent) {
        mc.netHandler ?: return

        if (!inBus.isEmpty() && ((fakeLagMoveOnly.get() && !MovementUtils.isMoving()) || inTimer.hasTimePassed(inDelay.toLong()))) {
            while (inBus.size > 0)
                inBus.poll()?.processPacket(mc.netHandler)
            inDelay = RandomUtils.nextInt(minRand.get(), maxRand.get())
            inTimer.reset()
        }
        if (!outBus.isEmpty() && ((fakeLagMoveOnly.get() && !MovementUtils.isMoving()) || outTimer.hasTimePassed(
                outDelay.toLong()
            ))
        ) {
            while (outBus.size > 0) {
                val upPacket = outBus.poll() ?: continue
                PacketUtils.sendPacketNoEvent(upPacket)
            }
            outDelay = RandomUtils.nextInt(minRand.get(), maxRand.get())
            outTimer.reset()
        }
    }
}
